package com.nidu.demo.auth;

import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.alibaba.cola.exception.BizException;
import com.google.common.annotations.VisibleForTesting;
import com.nidu.demo.auth.dto.*;
import com.nidu.demo.auth.api.SysOAuth2Service;
import com.nidu.demo.common.constants.Constants;
import com.nidu.demo.common.constants.RequestHeaderConstants;
import com.nidu.demo.common.enums.BooleanEnum;
import com.nidu.demo.common.enums.LoginTypeEnum;
import com.nidu.demo.common.enums.UserTypeEnum;
import com.nidu.demo.common.exception.ErrorCodeConstants;
import com.nidu.demo.auth.model.LoginEvent;
import com.nidu.demo.log.gateway.LoginLogGateway;
import com.nidu.demo.log.model.LoginLog;
import com.nidu.demo.oauth2.ability.OAuth2AccessTokenDomainService;
import com.nidu.demo.oauth2.ability.OAuth2ClientDomainService;
import com.nidu.demo.oauth2.ability.OAuth2RefreshTokenDomainService;
import com.nidu.demo.oauth2.convertor.OAuth2AccessTokenConvertor;
import com.nidu.demo.oauth2.model.OAuth2AccessToken;
import com.nidu.demo.oauth2.model.OAuth2Client;
import com.nidu.demo.oauth2.model.OAuth2RefreshToken;
import com.nidu.demo.security.core.LoginUser;
import com.nidu.demo.security.util.SecurityUtils;
import com.nidu.demo.user.ability.UserDomainService;
import com.nidu.demo.user.gateway.UserGateway;
import com.nidu.demo.user.model.User;
import com.xingyuv.captcha.model.common.ResponseModel;
import com.xingyuv.captcha.model.vo.CaptchaVO;
import com.xingyuv.captcha.service.CaptchaService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

import java.util.Objects;

@Slf4j
@Service
@RequiredArgsConstructor
public class SysOAuth2ServiceImpl implements SysOAuth2Service {

    private final OAuth2AccessTokenConvertor accessTokenConvertor;

    private final UserGateway userGateway;

    private final UserDomainService userDomainService;

    private final OAuth2ClientDomainService clientDomainService;

    private final OAuth2RefreshTokenDomainService refreshTokenDomainService;

    private final OAuth2AccessTokenDomainService accessTokenDomainService;

    private final CaptchaService captchaService;

    private final LoginLogGateway loginLogGateway;

    private final ApplicationEventPublisher eventPublisher;

    /**
     * 验证码的开关，默认为 true
     */
    @Value("${cola.captcha.enable:true}")
    private Boolean captchaEnable;

    @Override
    public SingleResponse<AuthLoginCO> login(AuthLoginCmd cmd) {

        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

        String clientIP = ServletUtil.getClientIP(request);
        if ("0.0.0.0.0.0.1".equals(clientIP) || "::1".equals(clientIP)) {
            clientIP = "127.0.0.1";
        }
        // 校验验证码
        validateCaptcha(cmd.getCaptchaVerification());

        // 验证客户端
        OAuth2Client client = clientDomainService.validateClient(Constants.DEFAULT_AUTH_CLIENT);

        // 使用账号密码，进行登录
        User user;
        try {
            user = userDomainService.authenticate(cmd.getUsername(), cmd.getPassword());
        } catch (Exception e) {
            // 登录失败，记录登录日志
            publishLoginLogEvent(new User().setUsername(cmd.getUsername()), clientIP, request.getHeader(RequestHeaderConstants.USER_AGENT), false);
            throw e; // 重新抛出异常
        }

        // 构建登录用户
        LoginUser loginUser = LoginUser.create(user.getId(), UserTypeEnum.ADMIN.getType(), user.getTenantId(), client.getScopes());
        SecurityUtils.setLoginUser(loginUser, request);

        // 创建刷新令牌和访问令牌
        OAuth2AccessToken accessToken = accessTokenDomainService.createToken(user.getId(), UserTypeEnum.ADMIN.getType(),
                client.getId(), client.getScopes(), client.getAccessTokenValiditySeconds(), client.getRefreshTokenValiditySeconds());
        AuthLoginCO loginCO = accessTokenConvertor.toAuthLoginCO(accessToken);

        // 发布登录事件
        publishLoginLogEvent(user, clientIP, request.getHeader(RequestHeaderConstants.USER_AGENT), BooleanEnum.TRUE.getValue()); // 1表示登录成功

        return SingleResponse.of(loginCO);
    }

    @Override
    public Response logout(String token) {
        // 删除访问令牌
        accessTokenDomainService.removeAccessToken(token);
        return Response.buildSuccess();
    }

    @Override
    public Response sendSmsCode(AuthSmsSendCmd cmd) {
        // TODO: 实现发送短信验证码逻辑
        return Response.buildSuccess();
    }

    @Override
    public SingleResponse<AuthLoginCO> smsLogin(AuthSmsLoginCmd cmd) {
        // TODO: 实现短信登录逻辑
        return null;
    }

    @Override
    public SingleResponse<AuthLoginCO> socialLogin(AuthSocialLoginCmd cmd) {
        // TODO: 实现社交登录逻辑
        return null;
    }

    @Override
    public SingleResponse<AuthLoginCO> refreshToken(String refreshToken) {
        // 验证客户端
        OAuth2Client client = clientDomainService.validateClient(Constants.DEFAULT_AUTH_CLIENT);
        // 验证刷新令牌
        OAuth2RefreshToken auth2RefreshToken = refreshTokenDomainService.validateRefreshToken(refreshToken, client.getId());
        // 刷新访问令牌
        OAuth2AccessToken accessToken = accessTokenDomainService.refreshAccessToken(SecurityUtils.getLoginUserId(), UserTypeEnum.ADMIN.getType(),
                auth2RefreshToken.getRefreshToken(), auth2RefreshToken.getClientId(), auth2RefreshToken.getScopes(), client.getAccessTokenValiditySeconds());
        AuthLoginCO loginCO = accessTokenConvertor.toAuthLoginCO(accessToken);
        return SingleResponse.of(loginCO);
    }

    @Override
    public SingleResponse<AuthLoginCO> register(AuthRegisterCmd cmd) {
        // 1. 校验验证码
        validateCaptcha(cmd.getCaptchaVerification());

        // 2. 校验用户名是否已存在
        User user = User.create(cmd.getUsername(), cmd.getPassword(), cmd.getNickname(), cmd.getMobile(), cmd.getEmail());

        userDomainService.validateDuplicate(cmd.getUsername(), cmd.getMobile(), cmd.getEmail(), null);
        User save = userGateway.create(user);
        // 3. 直接登录
        // 验证客户端
        OAuth2Client client = clientDomainService.validateClient(Constants.DEFAULT_AUTH_CLIENT);

        // 创建刷新令牌和访问令牌
        OAuth2AccessToken accessToken = accessTokenDomainService.createToken(save.getId(), UserTypeEnum.ADMIN.getType(), client.getId(),
                client.getScopes(), client.getAccessTokenValiditySeconds(), client.getRefreshTokenValiditySeconds());
        AuthLoginCO loginCO = accessTokenConvertor.toAuthLoginCO(accessToken);

        // 注册成功后也记录登录日志
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String clientIP = ServletUtil.getClientIP(request);
        if ("0.0.0.0.0.0.1".equals(clientIP) || "::1".equals(clientIP)) {
            clientIP = "127.0.0.1";
        }
        publishLoginLogEvent(save, clientIP, request.getHeader(RequestHeaderConstants.USER_AGENT), true); // 注册成功视为登录成功

        return SingleResponse.of(loginCO);
    }

    @VisibleForTesting
    void validateCaptcha(String captchaVerification) {
        // 如果验证码关闭，则不进行校验
        if (!captchaEnable) {
            return;
        }
        // 校验验证码
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(captchaVerification);
        ResponseModel responseModel = captchaService.verification(captchaVO);
        // 验证不通过
        if (!responseModel.isSuccess()) {
            throw new BizException(ErrorCodeConstants.LOGIN_CAPTCHA_CODE_ERROR.getCode(), ErrorCodeConstants.LOGIN_CAPTCHA_CODE_ERROR.getMessage());
        }
    }

    /**
     * 创建登录日志并发布异步事件
     */
    private void publishLoginLogEvent(User user, String clientIP, String userAgent, Boolean result) {
        try {
            // 创建登录日志
            LoginLog loginLog = LoginLog.create(LoginTypeEnum.LOGIN_USERNAME.getType(), null, user.getId(),
                    UserTypeEnum.ADMIN.getType(), user.getUsername(), result, clientIP, userAgent);

            // 发布登录日志事件，异步处理
            eventPublisher.publishEvent(new LoginEvent(this, loginLog));

        } catch (Exception e) {
            // 登录日志创建失败不应该影响正常登录流程，只记录错误日志
            log.error("创建登录日志失败: {}", e.getMessage());
        }
    }

}
